﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using DotNetX;

namespace RPGX.Graphics
{
    public class HUDObject_TextBox : HUDObject
    {
        #region Properties

        public override string Name
        {
            get
            {
                return "TextBox";
            }
        }

        public Texture2D Texture
        {
            get { return _texture; }
            set { _texture = value; }
        }
        private Texture2D _texture = null;

        public Texture2D CaretTexture
        {
            get { return _caretTexture; }
            set { _caretTexture = value; }
        }
        private Texture2D _caretTexture = null;

        public string Text
        {
            get
            {
                string s = String.Empty;

                if (String.IsNullOrEmpty(this._text))
                {
                    s = " "; // the default should be at least a space so that we can measure the string's height
                }
                else
                {
                    s = this._text;
                }

                return s;
            }
            set { this._text = value; }
        }
        private string _text = String.Empty;

        public bool IsShiftDown
        {
            get { return _isShiftDown; }
            set { _isShiftDown = value; }
        }
        private bool _isShiftDown = false;

        public bool IsCapsLockOn
        {
            get { return _isCapsLockOn; }
            set { _isCapsLockOn = value; }
        }
        private bool _isCapsLockOn = false;

        public int CursorPosition
        {
            get { return _cursorPosition; }
            set { _cursorPosition = value; }
        }
        private int _cursorPosition = 0;

        public DateTime LastCaretRenderTime
        {
            get { return _lastCaretRenderTime; }
            set { _lastCaretRenderTime = value; }
        }
        private DateTime _lastCaretRenderTime = DateTime.MinValue;

        public bool ShouldRenderCaret
        {
            get { return _shouldRenderCaret; }
            set { _shouldRenderCaret = value; }
        }
        private bool _shouldRenderCaret = false;

        private int WidthInCharacters
        {
            get
            {
                Vector2 singleSize = this.HUD.Text.CurrentFont.MeasureString(" ");

                return this.Width / (int)singleSize.X;
            }
        }

        public int HorizontalScrollOffset
        {
            get { return _horizontalScrollOffset; }
            set { _horizontalScrollOffset = value; }
        }
        private int _horizontalScrollOffset = 0;

        public bool IsPassword
        {
            get { return _isPassword; }
            set { _isPassword = value; }
        }
        private bool _isPassword = false;

        #endregion

        #region Public Methods

        public override bool Render(GameTime time, SpriteBatch spriteBatch, Rectangle bounds)
        {
            if (this.Texture == null)
                return false;

            Vector2 size = this.HUD.Text.CurrentFont.MeasureString(this.Text);
            Vector2 singleSize = this.HUD.Text.CurrentFont.MeasureString(" ");

            if (this.Height < size.Y)
                this.Height = (int)size.Y;

            Rectangle pos = new Rectangle();
            pos.X = bounds.X + (int)this.PositionRelativeToParent.X;
            pos.Y = bounds.Y + (int)this.PositionRelativeToParent.Y;
            pos.Width = this.Width;
            pos.Height = this.Height;

            spriteBatch.Draw(this.Texture, pos, Color.White);

            string textToRender = this.Text;

            int caretX = this.CursorPosition;
            if (caretX > this.WidthInCharacters - 1)
            {
                this.HorizontalScrollOffset = (caretX - this.WidthInCharacters + 1) * (int)singleSize.X;
            }
            else
            {
                this.HorizontalScrollOffset = 0;
            }

            int subStringStartIdx = 0;

            if (this.HorizontalScrollOffset > 0)
            {
                subStringStartIdx = this.HorizontalScrollOffset / (int)singleSize.X;
            }

            if (textToRender.Length > this.WidthInCharacters)
            {
                textToRender = textToRender.Substring(subStringStartIdx, this.WidthInCharacters);
            }

            if (this.IsPassword)
            {
                string p = String.Empty;
                foreach (char c in textToRender)
                {
                    p += "*";
                }

                textToRender = p;

                // because the first character is a space - a strange bug I should fix
                if (this.HorizontalScrollOffset == 0)
                {
                    textToRender = String.Format(" {0}", textToRender.Remove(0, 1));
                }
            }

            spriteBatch.DrawString(this.HUD.Text.CurrentFont, textToRender, new Vector2(pos.X, pos.Y), Color.White);

            this.RenderCaret(spriteBatch);

            foreach (var c in this.ChildObjects)
            {
                c.Render(time, spriteBatch, pos);
            }

            return true;
        }

        public override bool FromXML(XElement element, HUD hud)
        {
            this.ChildObjects = hud.LoadChildElements(element, this);

            string texture = element.Attribute("texture").Value;

            this.Texture = hud.Graphics.Game.Content.Load<Texture2D>(texture);

            string carettexture = element.Attribute("carettexture").Value;

            this.CaretTexture = hud.Graphics.Game.Content.Load<Texture2D>(carettexture);

            var isPassword = element.Attribute("isPassword");
            if (isPassword != null && !String.IsNullOrEmpty(isPassword.Value))
            {
                this.IsPassword = isPassword.Value.ToBooleanOrDefault(false);
            }

            return true;
        }

        public override bool UnLoad()
        {
            this.Texture.Dispose();
            this.Texture = null;

            return true;
        }

        #region Events

        public override void OnKeyDown(HUDKeyDownEventArgs e)
        {
            base.OnKeyDown(e);

            if (e.Keys.Contains(Keys.LeftShift) || e.Keys.Contains(Keys.RightShift))
                this.IsShiftDown = true;
        }

        public override void OnKeyUp(HUDKeyUpEventArgs e)
        {
            base.OnKeyUp(e);

            if (e.Keys.Contains(Keys.LeftShift) || e.Keys.Contains(Keys.RightShift))
                this.IsShiftDown = false;
        }

        public override void OnKeyPressed(HUDKeyPressedEventArgs e)
        {
            base.OnKeyPressed(e);

            foreach (Keys k in e.Keys)
            {
                if (this.IsKeySystem(k))
                {
                    this.SetSystemKeyState(k);
                }

                if (this.IsKeyAlphaNumeric(k))
                {
                    this.AddKeyToText(k);
                }
            }
        }

        #endregion

        #endregion

        #region Private Methods

        private bool IsKeySystem(Keys k)
        {
            return k == Keys.LeftShift ||
                   k == Keys.RightShift ||
                   k == Keys.CapsLock ||
                   k == Keys.Delete ||
                   k == Keys.Back ||
                   k == Keys.Left ||
                   k == Keys.Right;
        }

        private void SetSystemKeyState(Keys k)
        {
            if (k == Keys.CapsLock)
            {
                this.IsCapsLockOn = !this.IsCapsLockOn;
            }
            else if (k == Keys.Back)
            {
                if (this.CursorPosition > 0)
                {
                    this.Text = this.Text.Remove(this.CursorPosition, 1);

                    this.CursorPosition--;
                }
            }
            else if (k == Keys.Delete)
            {
                if (this.CursorPosition + 1 < this.Text.Length)
                {
                    this.Text = this.Text.Remove(this.CursorPosition + 1, 1);
                }
            }
            else if (k == Keys.Left)
            {
                if (this.CursorPosition > 0)
                {
                    this.CursorPosition--;
                }
            }
            else if (k == Keys.Right)
            {
                if (this.CursorPosition < this.Text.Length - 1)
                {
                    this.CursorPosition++;
                }
            }
        }

        private bool IsKeyAlphaNumeric(Keys k)
        {
            return (k >= Keys.A &&
                    k <= Keys.Z) ||
                   (k >= Keys.D0 &&
                    k <= Keys.D9) ||
                    (k >= Keys.NumPad0 &&
                    k <= Keys.NumPad9) ||
                    k == Keys.Tab ||
                    k == Keys.Space ||
                    k == Keys.OemComma ||
                    k == Keys.OemPeriod ||
                    k == Keys.OemQuestion ||
                    k == Keys.OemCloseBrackets ||
                    k == Keys.OemMinus ||
                    k == Keys.OemOpenBrackets ||
                    k == Keys.OemPipe ||
                    k == Keys.OemPlus ||
                    k == Keys.OemTilde ||
                    k == Keys.OemQuotes;
        }

        private void AddKeyToText(Keys k)
        {
            string s = String.Empty;

            if (k >= Keys.A && k <= Keys.Z)
            {
                if (this.IsCapsLockOn || this.IsShiftDown)
                    s += ((char)k).ToString().ToUpper();
                else
                    s += ((char)k).ToString().ToLower();
            }
            else if (k >= Keys.D0 && k <= Keys.D9)
            {
                if (k == Keys.D1) s += this.IsShiftDown ? "!" : "1"; else
                if (k == Keys.D2) s += this.IsShiftDown ? "\"" : "2"; else
                if (k == Keys.D3) s += this.IsShiftDown ? "$" : "3"; else // TODO find out how to get the £ symbol working
                if (k == Keys.D4) s += this.IsShiftDown ? "$" : "4"; else
                if (k == Keys.D5) s += this.IsShiftDown ? "%" : "5"; else
                if (k == Keys.D6) s += this.IsShiftDown ? "^" : "6"; else
                if (k == Keys.D7) s += this.IsShiftDown ? "&" : "7"; else
                if (k == Keys.D8) s += this.IsShiftDown ? "*" : "8"; else
                if (k == Keys.D9) s += this.IsShiftDown ? "(" : "9"; else
                if (k == Keys.D0) s += this.IsShiftDown ? ")" : "0";
            }
            else if (k >= Keys.NumPad0 && k <= Keys.NumPad9)
            {
                s += ((int)(k - Keys.NumPad0)).ToString();
            }
            else if (k == Keys.Tab)
            {
                s += "    ";
            }
            else if (k == Keys.Space)
            {
                s += " ";
            }
            else if (k == Keys.OemComma)
            {
                s += this.IsShiftDown ? "<" : ",";
            }
            else if (k == Keys.OemPeriod)
            {
                s += this.IsShiftDown ? ">" : ".";
            }
            else if (k == Keys.OemQuestion)
            {
                s += this.IsShiftDown ? "?" : "/";
            }
            else if (k == Keys.OemCloseBrackets)
            {
                s += this.IsShiftDown ? "}" : "]";
            }
            else if (k == Keys.OemMinus)
            {
                s += this.IsShiftDown ? "_" : "-";
            }
            else if (k == Keys.OemOpenBrackets)
            {
                s += this.IsShiftDown ? "{" : "[";
            }
            else if (k == Keys.OemPipe)
            {
                s += this.IsShiftDown ? "|" : "\\";
            }
            else if (k == Keys.OemPlus)
            {
                s += this.IsShiftDown ? "+" : "=";
            }
            else if (k == Keys.OemTilde)
            {
                s += this.IsShiftDown ? "@" : "'"; // keep with the UK keyboard...
            }
            else if (k == Keys.OemQuotes)
            {
                s += this.IsShiftDown ? "~" : "#";
            }

            this.Text = this.Text.Insert(this.CursorPosition + 1, s);

            this.CursorPosition += s.Length;
        }

        private void RenderCaret(SpriteBatch spriteBatch)
        {
            Vector2 singleCharacterSize = this.HUD.Text.CurrentFont.MeasureString(" ");

            if (this.IsFocused)
            {
                DateTime caretRenderDate = DateTime.Now;
                TimeSpan caretTimeSpan = caretRenderDate - this.LastCaretRenderTime;
                if (caretTimeSpan.Milliseconds >= 500)
                {
                    this.ShouldRenderCaret = !this.ShouldRenderCaret;

                    this.LastCaretRenderTime = caretRenderDate;
                }
            }
            else
            {
                this.ShouldRenderCaret = true;
            }

            if (this.ShouldRenderCaret)
            {
                Rectangle caretPos = new Rectangle();
                caretPos.X = (this.CursorPosition + 1) * (int)singleCharacterSize.X + (int)this.PositionRelativeToParent.X - this.HorizontalScrollOffset;
                caretPos.Y = 1 + (int)this.PositionRelativeToParent.Y;
                caretPos.Width = 2;
                caretPos.Height = (int)singleCharacterSize.Y - 1;

                spriteBatch.Draw(this.CaretTexture, caretPos, Color.White);
            }
        }

        #endregion
    }
}
